现在让我们来看下Spring AOP如何处理通知。

12.3.1 Adcive lifecycles

每个通知都是Spring bean。一个通知可以被多个被通知的对象共享,也可以仅由一个对象使用。这取决于通知是针对每个类还是每个实例。
针对每个类的通知更常用。它适合像事务通知这样的常用通知。它们无法判断代理对象的状态或是添加新状态,只会对方法和参数产生影响。
针对每个实例的通知适合引入。这种情况下,通知为代理对象添加了新的状态。
可以在同一个AOP代理中混合使用共享和针对实例的通知。

12.3.2 Advice types in Spring

Spring提供了几种可以直接用的通知类型,并且都支持被扩展。来看一下这些通知的概念和标准。

Interception around advice

Spring中最基本的同志类型是interception around advice。
Spring使用方法拦截为around advice兼容AOP Alliance中的接口。通过方法拦截实现的around advice需要实现下列接口:

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

invoke()方法的MethodInvocation参数暴露了被调用的方法;目标连接点;AOP代理;和方法的参数。invoke()方法需要返回连接点返回的值。
下面是一个简单的MethodInterceptor实现:

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}

记得调用MethodInvocation的proceed()方法。这使得拦截链继续向下执行。多数拦截器都会调用这个方法, 并且将方法的返回值返回。然而拦截器可以像任何around advice一样,返回一个和procced方法结果不同的值或是抛出一个异常。当然,除非有原因,你不会这么做!

MethodIntercepors通常提供了和其他AOP Alliance兼容的AOP实现的互通性。本节其余讨论的别的通知类型以Spring特定的方式实现了普通的AOP概念。虽然使用具体的通知类型有一定优势,但是如果你希望在另一个AOP框架中使用这个切面,还是需要MethodInterceptor around advice。注意,切点目前不能在不同的框架中转换,AOP Alliance目前也没有定义切点接口。

Throws advice

如果连接点在运行时抛出一个异常,那么连接点返回后会调用Throws advice。Spring提供了throw advice的标志。注意这意味着org.springframework.aop.ThrowsAdvice接口不含有任何方法:它是一个标志接口,但是实现了这个接口的类至少要实现一个或多个throws advice方法。这些方法有以下形式:

afterThrowing([Method, args, target], subclassOfThrowable)

只有最后的参数是必须的。方法的签名必须要有这一到四个参数,取决于通知对什么方法和参数感兴趣。下面是一些throws advice的例子。
下面的通知在抛出RemoteException(包括子类)抛出时会被调用:

public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

最后这个例子说明了如何在一个类中使用其中两个的方法,来处理RemoteExcpetionServletException。任意数量的通知方法可以在一个类中使用:

public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

如果一个throws-advice自身抛出了异常,那么它会覆盖原始的异常(即修改了抛给用户的异常)。覆盖的异常通常是RuntimeException;这样能兼容任何方法的签名。然而,如果一个throws-advice方法抛出了受检查异常,那它必须要符合目标方法声明的异常来符合目标方法的签名。请不要抛出与目标方法签名不兼容的未声明的受检查异常。
Throws advice可以用于任何切点。

After Returning advice

Spring中的after returning advice方法必须要实现下面的org.springframework.aop.AfterReturningAdvice接口:

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}

after returning adivce可以访问方法的返回值(但不能修改),方法,方法的参数和目标对象。
下列after returning advice统计了执行成功未抛出异常的方法调用次数:

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}

这个通知无法改变方法的执行路径。如果它抛出了一个异常,那么异常会取代返回值会在拦截器链被传递。

After returning advice可以用于任何切点。

Introduction advice

Spring将introduction advice看作一种特殊的interception advice。
引入需要IntroductionAdvisor,和一个IntroductionInterceptor

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

引入必须要实现从AOP Alliance的MethodInterceptor接口集成来的invoke()方法:也就是如果被拦截的方法在引入接口中,引入接口必须要负责调用这个方法——它不能够调用invoke()
引入接口不能用于任意切点,它只能在类上使用,而非方法层面。你只能结合IntroductionAdvisor使用,它定义了下面的方法:

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class[] getInterfaces();
}

它没有MethodMatcher,因此也没有Pointcut。只有类过滤的逻辑。
getInterfaces()方法返回了需要被advisor引入的接口。
validateInterfaces()方法用来在内部验证接口是否可以被配置的IntroductionInterceptor实现。
让我们看一个来自Spring test组件的例子。假设我们希望为一个或多个对象引入以下接口:

public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}

不管对象是什么类型,我们希望被通知的对象可以被转换成Lockable,调用lock和unlock方法。当调用lock方法时,我们希望所有的setter都会抛出LockedException。因此,我们通过添加一个切面在不改变对象,甚至不了解对象的情况下为他增加了这个功能。这是AOP的好例子: 首先,我们需要一个IntroductionInterceptor来完成繁重的工作。这里,我们继承了org.springframework.aop.support.DelegatingIntroductionInterceptor。我们可以自己实现Interceptor,但是多数情况下使用DelegatingIntroductionInterceptor是更好的选择。
DelegatingIntroductionInterceptor被设计用来将引入分发给被引入接口真正的实现去完成,隐藏了实现这个动作的拦截器。委托可以通过构造器的参数设置给任意的对象,默认的委托(无参的构造器)是给自己。因此下面的例子中,委托是给DelefatingIntroductionInterceptor的子类LockMixin。给定一个委托(默认是自身),DelegatingIntroductionInterceptor的实例会查找委托实现的全部接口(除了IntroductionInterceptor),并为它们实现引入。像LockMixin这样的子类可以调用suppressInterface(Class intf)来替换不被暴露的方法。然而不管IntroductionInterceptor准备支持多少接口,所使用的IntroductionAdvisor只会控制实际暴露的接口。引入的接口会隐藏目标实现的相同的接口。因此,LockMixin继承了DelegatingInterceptor并实现了Lockable接口。父类会自动监测Lockable接口可以被引入,而不需要自己指定。我们可以通过这个方式引入任意数量的接口。
注意实例变量locked的使用。它为目标对象添加了额外的状态。

public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}

通常不需要覆盖invoke()方法,而是直接用DelegatingIntroductionInterceptor中的实现——如果方法是引入的,将其交给委托处理,否则直接调用。这通常就足够了。这个例子中,我们加了一个检查:在被锁的情况,不能够调用setter方法。
Introduction advisor所需要的很简单。所要做的就是持有一个LockMinxin实例,并指出引入的接口。这个例子中,就是Lockable。更富在的例子可能会引入一个Introduction Interceptor的引用(将被定义成原型):这个例子中,LockMinxin不需要这样的配置,我们只用简单用new创建它。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}

我们可以很简答的应用这个advisor:不需要任何配置。(然而,在Introduction advisor之外使用IntroductionInterceptor是不可以的)。对于引入,由于它是具有状态的,因此通常的针对每个实例的。对于每个被通知的对象,我们需要一个不同的LockMixinAdvisor,因此也要有不同的LockMixin。advisor包含了对通知的对象的状态。
我们可以用编码的方式应用这个通知,Advised.addAdvisor(),或是(通常更建议)用XML的方式配置,像配置其他advisor那样。下面讨论的所有代理创建选择,(包括“自动代理创建者”)都正确处理引入和状态的混合。